#![no_std]

//! Shared ioctl definitions for the Starry sandbox tooling.
//!
//! This crate is consumed by both the kernel (to implement the device-side
//! handlers) and user space utilities (to issue requests via `ioctl`).

use bitflags::bitflags;

const IOC_NRBITS: u32 = 8;
const IOC_TYPEBITS: u32 = 8;
const IOC_SIZEBITS: u32 = 14;
const IOC_DIRBITS: u32 = 2;

const IOC_NRSHIFT: u32 = 0;
const IOC_TYPESHIFT: u32 = IOC_NRSHIFT + IOC_NRBITS;
const IOC_SIZESHIFT: u32 = IOC_TYPESHIFT + IOC_TYPEBITS;
const IOC_DIRSHIFT: u32 = IOC_SIZESHIFT + IOC_SIZEBITS;

const IOC_NONE: u32 = 0;
const IOC_WRITE: u32 = 1;
const IOC_READ: u32 = 2;
const IOC_READ_WRITE: u32 = IOC_READ | IOC_WRITE;

const fn ioctl_cmd(dir: u32, ty: u32, nr: u32, size: u32) -> u32 {
    (dir << IOC_DIRSHIFT) | (ty << IOC_TYPESHIFT) | (nr << IOC_NRSHIFT) | (size << IOC_SIZESHIFT)
}

/// GPIO control protocol.
pub mod gpio {
    use super::ioctl_cmd;

    /// Magic number used for GPIO ioctls (matches `b'G'`).
    pub const IOCTL_TYPE: u32 = b'G' as u32;

    /// Issue a write to set the level of the given GPIO pin.
    pub const IOCTL_SET: u32 = ioctl_cmd(
        super::IOC_WRITE,
        IOCTL_TYPE,
        0x01,
        core::mem::size_of::<Request>() as u32,
    );

    /// Read the level of the given GPIO pin.
    pub const IOCTL_GET: u32 = ioctl_cmd(
        super::IOC_READ,
        IOCTL_TYPE,
        0x02,
        core::mem::size_of::<Request>() as u32,
    );

    /// Toggle the given GPIO pin.
    pub const IOCTL_TOGGLE: u32 = ioctl_cmd(
        super::IOC_WRITE,
        IOCTL_TYPE,
        0x03,
        core::mem::size_of::<Request>() as u32,
    );

    /// GPIO level values understood by the kernel driver.
    #[repr(u8)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub enum Level {
        Low  = 0,
        High = 1,
    }

    impl Level {
        pub const fn from_bool(high: bool) -> Self {
            if high { Self::High } else { Self::Low }
        }
    }

    /// Basic GPIO operation request/response.
    #[repr(C)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct Request {
        pub bank: u32,
        pub pin: u32,
        pub level: Level,
        _reserved: [u8; 3],
    }

    impl Request {
        pub const fn new(bank: u32, pin: u32, level: Level) -> Self {
            Self {
                bank,
                pin,
                level,
                _reserved: [0; 3],
            }
        }
    }
}

/// Timer control protocol for the RK3588 PMU timer block.
pub mod timer {
    use super::{IOC_READ, IOC_READ_WRITE, IOC_WRITE, ioctl_cmd};

    /// Magic number used for timer ioctls (matches `b'T'`).
    pub const IOCTL_TYPE: u32 = b'T' as u32;

    /// Initialise the timer channel before use.
    pub const IOCTL_INIT: u32 = ioctl_cmd(
        IOC_WRITE,
        IOCTL_TYPE,
        0x01,
        core::mem::size_of::<ChannelRequest>() as u32,
    );

    /// Disable a timer channel (mirrors `RK3588Timer::disable`).
    pub const IOCTL_DISABLE: u32 = ioctl_cmd(
        IOC_WRITE,
        IOCTL_TYPE,
        0x02,
        core::mem::size_of::<ChannelRequest>() as u32,
    );

    /// Wait for the timer to reach the requested tick count.
    ///
    /// This call blocks until the counter reaches the specified number of
    /// ticks. It returns the number of ticks observed when the wait completed
    /// (which is helpful for diagnostics if the timer wrapped).
    pub const IOCTL_WAIT_TICKS: u32 = ioctl_cmd(
        IOC_READ_WRITE,
        IOCTL_TYPE,
        0x03,
        core::mem::size_of::<WaitRequest>() as u32,
    );

    /// Wait for a number of microseconds using the hardware timer as the time
    /// base. The kernel converts the microseconds into ticks internally.
    pub const IOCTL_WAIT_MICROS: u32 = ioctl_cmd(
        IOC_READ,
        IOCTL_TYPE,
        0x04,
        core::mem::size_of::<MicrosRequest>() as u32,
    );

    /// Request that only carries the timer channel index.
    #[repr(C)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct ChannelRequest {
        pub channel: u32,
        _reserved: [u8; 12],
    }

    impl ChannelRequest {
        pub const fn new(channel: u32) -> Self {
            Self {
                channel,
                _reserved: [0; 12],
            }
        }
    }

    /// Wait request that carries the target number of ticks.
    #[repr(C)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct WaitRequest {
        pub channel: u32,
        pub ticks: u64,
        /// Updated by the kernel with the number of ticks observed.
        pub observed: u64,
    }

    impl WaitRequest {
        pub const fn new(channel: u32, ticks: u64) -> Self {
            Self {
                channel,
                ticks,
                observed: 0,
            }
        }
    }

    /// Wait request expressed directly in microseconds.
    #[repr(C)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct MicrosRequest {
        pub channel: u32,
        pub micros: u64,
        pub observed: u64,
    }

    impl MicrosRequest {
        pub const fn new(channel: u32, micros: u64) -> Self {
            Self {
                channel,
                micros,
                observed: 0,
            }
        }
    }
}

/// Power management (PMU) protocol, currently limited to reboot requests.
pub mod pmu {
    use super::{IOC_NONE, IOC_WRITE, bitflags, ioctl_cmd};

    /// Magic number used for PMU ioctls (matches `b'P'`).
    pub const IOCTL_TYPE: u32 = b'P' as u32;

    /// Trigger a system reboot through the CRU.
    pub const IOCTL_REBOOT: u32 = ioctl_cmd(
        IOC_WRITE,
        IOCTL_TYPE,
        0x01,
        core::mem::size_of::<RebootRequest>() as u32,
    );

    bitflags! {
        /// Additional reboot options exposed to user space.
        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
        pub struct RebootFlags: u32 {
            /// Keep the reset counter threshold unchanged.
            const KEEP_THRESHOLD = 0x0;
            /// Request that the driver uses the provided threshold value.
            const SET_THRESHOLD = 0x1;
        }
    }

    /// Reboot levels exposed by the driver.
    #[repr(u32)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub enum RebootLevel {
        First  = 0,
        Second = 1,
    }

    impl RebootLevel {
        pub const fn from_raw(raw: u32) -> Option<Self> {
            match raw {
                0 => Some(Self::First),
                1 => Some(Self::Second),
                _ => None,
            }
        }
    }

    /// Reboot request payload.
    #[repr(C)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct RebootRequest {
        pub level: RebootLevel,
        pub flags: RebootFlags,
        pub threshold: u32,
        _reserved: u32,
    }

    impl RebootRequest {
        pub const fn new(level: RebootLevel) -> Self {
            Self {
                level,
                flags: RebootFlags::from_bits_truncate(IOC_NONE),
                threshold: 0,
                _reserved: 0,
            }
        }
    }
}
